#!/usr/bin/lua
local isWin=string.find(string.lower(os.getenv('OS') or 'nil'),'windows')~=nil
if isWin then
	package.path =os.getenv('HOMEDRIVE')..os.getenv('HOMEPATH').."/bin/?.lua" .. ";"..package.path 
	package.path =os.getenv('HOME').."/bin/?.lua" .. ";"..package.path 
else
	package.path =os.getenv('HOME').."/bin/?.lua" .. ";"..package.path 
end
require("mylib")
 
-- order doesn't matter
g_etagsSrc={ "%.bvh$", "%.rb$", "%.m$", "%.c$", "%.cpp$", "%.h$","%.hpp$", "%.inl$", "%.py$", "%.lua$", "%.java$"}
g_customEtagsSrc={"%.lua$"}
g_customEtagsPattern=
	 '-Re --langdef=luna --langmap=luna:.lua --regex-luna="/[ \\t]*([a-zA-Z0-9_\\-]+)[ \\t]*=[ \\t]*LUAclass\\(/\\1/s,struct/"'
g_vimSrc={"%.mm$","%.material$", "%.rb$", "%.m$", "Makefile$","%.bib$", "%.tex$", "%.wiki$","%.EE$", "%.wrl$", "%.lua$","%.py$", "%.c$", "%.h$", "%.hpp$", "%.txt$", "%.inl$", "%.cpp$"}
g_grepSrc=array.concatMulti(g_etagsSrc, {"%.tex$", "%.txt$"})
--g_cache_folder='/tmp/'
g_cache_folder=os.home_path()..'/.config/'
g_tmp_folder='/tmp/'
g_vimpath='vim'
g_gvimpath='gvim'
g_ctags='ctags'
-- ignore files that match following patterns even if they are in the repository.
g_ignorePattern={'^BaseLib/image/FreeImage/Dist/','^dependencies/'}
g_commitIgnorePattern={'^$','ogreconfig.txt', 'ogreconfig_linux.txt', 'debug_plot%.', 'OgreFltkConsole.exe', 'console.xml', 'OgreFltkConsoleJIT.exe','optimizelog'}
g_tagFallbackPath={}
g_debug=false
g_useEmacs=false
g_useSublime=false
function isWindows() 
	return os.isWindows() and not os.isCygwin()
end
if isWindows() then
	-- on some systems, os.execute doesn't work perfectly. so  this is a workaround.
	os.execute_original=os.execute
	function os.execute(cmd)
		local fn= os.createUnnamedBatchFile({cmd})
		os.execute_original('"'..fn..'"')
	end
end
function execute2(...)
	local tbl={...}
	for i,v in pairs(tbl) do
		print(v)
	end
	os.execute2(unpack(tbl))
end
if os.isApple() then
	g_ctags='/usr/local/bin/ctags' -- install exuberant ctags using brew : brew install ctags-exuberant
end
-- uncomment
function macro(fn, key)
	key=key or 'dos'
	print('macro '..key..' : uncommenting --##'..key)
	local contents=util.readFile(fn)
	util.writeFile(fn..".bak", contents)
	contents=string.gsub(contents,"--##"..key.." ", "--##"..key.."\n")
	util.writeFile(fn, contents)
end

-- comment out again
function unmacro(fn, key)
	key=key or 'dos'
	print('unmacro '..key)
	local function printSample(contents)
		local a=string.find(contents, "--##"..key) or string.find(contents, "--##"..key)
		if a==nil then
			print('no change will be made')
		else
			print(string.sub(contents, a, a+30))
		end
	end
	local contents=util.readFile(fn)
	printSample(contents)
	util.writeFile(fn..".bak", contents)
	contents=string.gsub(contents,"--##"..key.."\n", "--##"..key.." ")
	contents=string.gsub(contents,"--##"..key.."\r\n", "--##"..key.." ")
	print('-->')
	printSample(contents)
	util.writeFile(fn, contents)
end

if g_debug then
	os.execute_old=os.execute
	function os.execute(cmd)
		print(cmd)
		os.execute_old(cmd)
	end
	os.capture_old=os.capture
	function os.capture(cmd, opt)
		print(cmd)
		return os.capture_old(cmd, opt)
	end
end

function printHelp()
	print([[
Usage: gitv <command> [option] [<args>]

The most commonly used gitv commands are:
  --help     Show this page.
  --emacs    Use emacs instead of vim. (works for "vir", "ts" and "open" commands)
  --sublime  Use sublime text instead of vim. (works for "vir", "ts" and "open" commands)
  vi         Open a file in the git repository whose name contains 
             the keyword using vim. When no keyword is given, history will be shown.
               $ gitv vi [option] keyword
             or
               $ gitv vi [option]
             option -l: search only subdirectories of the current directory.
  vir        Same as "vi" except that an existing vim server is used when available.
  ts         Search a keyword in $GIT_ROOT/TAGS, and open using vim. $GIT_ROOT is automatically detected,
             and TAGS is generated if not there. 
             To edit main() function:
               $ gitv ts main$
  tl         List tags that match multiple patterns. The first pattern only matches the function name.
             You can skip the first keyword using "." pattern. To list all functions in class "motion":
               $ gitv tl . [^%a]motion::
  gvim       Open a file using gvim
  gvimr      Open a file using a gvim server. Supports multiple instances (experimental.)
               $ gitv gvimr GVIM1 a.txt
  gedit      Open a file using gedit
  choose     Write the name of a choosen file to ]]..g_cache_folder..[[chosen
               $ gitv choose keyword
  clear-hist Clear history. $GIT_ROOT/TAGS will be removed too.
  sync       Commit, pull, and then push. 
  sync .     Commit only the current folder (without recursing), pull, and then push.
  pull       Pull the current branch from all remotes
  fetch      Fetch the current branch from all remotes
  push       Push the current branch to all remotes
  commit     Interactively commit the current branch. "gitv ci" does the same.
  find       Find files 
               $ gitv find keyword
  grep       Unlike git grep, entire repository (not limited to the current 
             directory) will be searched. Files with allowed extensions will only
             be searched. (see ~/bin/gitv.) Lua patterns are used.
               $ gitv grep keyword
  findgrep   Find files and then grep
               $ gitv findgrep filepattern greppattern
  etags .    Generate TAGS for all tracked files in the current folder recursively
               $ gitv etags .
  etags      Generate TAGS for all tracked files in the git repository
  ctags      Generate .tags for all tracked files in the git repository
  diff       Show differences between commits. 
               $ gitv diff
               $ gitv diff HEAD~10
  undo-commit Revert the last commit (while retaining the local copy). 
              Do not undo-commit if you pushed your last commit!
  other experimental features: type gitv "command name" to see usage.
	  backup diffrepo  cprepo test_win_filename
  ]])
end

g_vimpath, g_gvimpath=os.findVIM()
g_gitpath=os.findGIT()

if isWindows() then
	g_cache_folder=os.home_path() ..'/'
end
function git()
	return g_gitpath or 'git'
end
function git_top() -- git_root
	if cache_git_top==nil then
		local t
		if os.isUnix() then
			local str=os.capture(git()..' rev-parse --show-toplevel 2>&1', true)
			if select(1,string.find(str, 'fatal:')) then
				str=''
			end
			t= string.lines(str)[1]
		else
			t= string.lines(os.capture(git()..' rev-parse --show-toplevel', true))[1]
		end
		cache_git_top=t
	end
	if cache_git_top=='' then
		print('Not a git repository. Searching the current folder...')
		return '.'
	end
	return cache_git_top
end 
git_tools={}
function string.prettyConcat(a,b,min_a)
	
	if #a<min_a then
		if #a==0 then
			return b
		end
		return a..string.rep(' ',min_a-#a)..b
	end
	return a..b
end

function git_tools.compareFileName(f1, f2)
	if f1==nil or f2==nil then
		dbg.console()
	end
	local _, count3 = string.gsub(f1, "%.%./", "")
	local _, count4 = string.gsub(f2, "%.%./", "")
	if count3==count4 then
		local _, count1 = string.gsub(f1, "/", "")
		local _, count2 = string.gsub(f2, "/", "")
		if count1==count2 then
			return string.lower(f1)<string.lower(f2)
			--local lf1, p2=os.processFileName(f1)
			--local lf2, p2=os.processFileName(f2)
			--return lf1<lf2
		end
		return count1<count2
	else
		return count3<count4
	end
end
function string.linesContaining(filename, pattern)
	local fout, msg=io.open(filename, "r")
	if fout==nil then
		print(msg)
		return
	end
	pattern=string.gsub(pattern,'[%$%^]','')
	local lastFileTag=''
	local t = {}
	while true do
		local line=fout:read('*line')
		if line==nil then break end
		if select(1, string.find(string.lower(line),string.lower(pattern))) then 
			table.insert(t, lastFileTag) 
			table.insert(t, line) 
		elseif not select(1, string.find(line, string.char(127))) then
			lastFileTag=line
		end
	end
	return t
end
function git_tools.searchTag(filename, searchKey) 
	local t=string.linesContaining(filename, searchKey)
	--local t=string.lines(util.readFile(filename))
	local currFile
	local tags={}
	local MAX_SEARCH=100000
	local isRegExp=false
	if select(1,string.find(searchKey, '%.')) 
		or select(1,string.find(searchKey, '%:')) 
		then
		isRegExp=true
	end

	for i=1,#t do
		local o=''
		local oo=string.tokenize(t[i],string.char(127))
		if #oo==1 then
			local oo2=string.tokenize(oo[1], ',')
			if #oo2==2 then
				--print(oo2[1], oo2[2])
				currFile=oo2[1]
			end
		else 
			local oo2 =string.tokenize(oo[2], string.char(1))
			local pattern=oo2[1]
			if isRegExp then
				-- when the searchKey contains "." ,
				-- for example, VRMLTransform.*translateBone,
				-- full function-signature is used as a pattern
				pattern=oo[1]
			end
			--print(oo2[1])
			--print(searchKey)
			--print(string.gsub(string.lower(searchKey), "%^",":"))
			if select(1,string.find(string.lower(pattern),string.lower(searchKey))) or
			 select(1,string.find(string.lower(pattern),string.gsub(string.lower(searchKey), "%^","[:%.]"))) then
				table.insert(tags, {pattern, oo[1], currFile, tonumber(string.tokenize(oo2[2],',')[1])})
				if #tags> MAX_SEARCH then
					printf("Error! max_search overflow")
					break
				end
			end
		end
	end
	do
		-- change relative folder.
		local fn, path=os.processFileName(filename)
		if path~='' then
			local tagRoot=os.relativeToAbsolutePath(path)
			local currDir=os.currentDirectory()

			--print(tagRoot, currDir)
			--print(fn, path)
			for i=1,#tags do
				local t=tags[i][3]
				if isWindows() then t=os.fromWindowsFileName(t) end
				t=os.relativeToAbsolutePath(t, tagRoot)
				t=os.absoluteToRelativePath(t, currDir)
				--print(tags[i][3], t)
				tags[i][3]=t
			end
		end
	end
	table.sort(tags, function(f11,f21)
		local f1=f11[3]
		local f2=f21[3]
		return git_tools.compareFileName(f1,f2)
	end)
	return tags
end
function git_tools.pull(path, password)
	if os.isFileExist(path..'/.git/HEAD') then
		local remotes=array.filter(function (a) return string.len(a)>1 end, string.lines(git_tools.capture('cd "'..path..'";git remote',true)))

		if arg[2] and arg[2]~='-p' then
			if string.sub(arg[2],1,1)==':' then
				password=string.sub(arg[2],2)
			else
				remotes=array.filter(function (a) return select(1,string.find(a,arg[2])) end, remotes)
			end
		end
		local curr_branch=array.filter(function (a) return string.sub(a,1,1)=='*' end ,  string.lines(git_tools.capture('cd "'..path..'";git branch',true)))[1]
		curr_branch=string.sub(curr_branch,2)
		for k,v in ipairs(remotes) do

			print(v)
			local cmd='cd "'..path..'";git pull '..v..curr_branch
			print('> '..cmd)
			git_tools.execute(cmd, password)
		end
	else
		print(path..'/.git/HEAD' .." doesn't exist")
	end
end
function git_tools.fetch(path)
	if os.isFileExist(path..'/.git/HEAD') then
		local remotes=array.filter(function (a) return string.len(a)>1 end, string.lines(git_tools.capture('cd "'..path..'";git remote',true)))
		if arg[2] then
			remotes=array.filter(function (a) return select(1,string.find(a,arg[2])) end, remotes)
		end
		local curr_branch=array.filter(function (a) return string.sub(a,1,1)=='*' end ,  string.lines(git_tools.capture('cd "'..path..'";git branch',true)))[1]
		curr_branch=string.sub(curr_branch,2)
		for k,v in ipairs(remotes) do

			print(v)
			local cmd='cd "'..path..'";git fetch '..v..curr_branch
			print('> '..cmd)
			git_tools.execute(cmd)
		end
	end
end
function git_tools.tokenize_cmd(cmd)
	local tokens=string.tokenize(cmd, ';')
	for i=1,#tokens do
		if string.sub(tokens[i],1,3)=="cd " then
			tokens[i]=string.gsub(tokens[i], '/','\\')
		end
	end
	return tokens
end
function git_tools.capture(cmd, isRaw)
	if isWindows() then
		local tokens=git_tools.tokenize_cmd(cmd)
		local fn=os.createUnnamedBatchFile(tokens, true)
		return os.capture('"'..fn..'"', isRaw)	
	else
		return os.capture(cmd, isRaw)
	end
end
function git_tools.execute(cmd, password)
	if isWindows() then
		local tokens=git_tools.tokenize_cmd(cmd)
		local fn=os.createUnnamedBatchFile(tokens, true)
		--os.execute('type _temp.bat')
		return os.execute_original('"'..fn..'"')
	else
		if password then
			local tt=string.tokenize(password,':')
			local passphrase
			if tt[2] then
				password=tt[1]
				passphrase=tt[2]
			end
			local cmd2=string.tokenize(cmd, ';')
		local script=[[
import pexpect
import sys
child=pexpect.spawn("]]..cmd2[#cmd2]..[[")
tbl=['password:', 'passphrase for key','Pull is not possible because you have unmerged files', 'up-to-date', 'Counting objects:','unexpectedly']
try:
	i=child.expect(tbl)
	print child.before,
	if i==0:
		child.sendline(']]..password..[[')
		print tbl[i]
		print '****'
		i=child.expect(tbl)
		print child.before,
	elif i==1:
		child.sendline('put key password here')
		print tbl[i]
		print '****'
		i=child.expect(tbl)
		print child.before,
	else:
		print tbl[i],
		print child.before,
	index=child.expect([pexpect.EOF, pexpect.TIMEOUT])
	while index==1:
		print(child.before)
		index=child.expect([pexpect.EOF, pexpect.TIMEOUT])
	print(child.before)
except:
	print('Pexpec Error after :')
	print child.before,
	print '::::'
	print(str(child))
]]
			util.writeFile('/tmp/temp.py', script)
			--local fn= os.createUnnamedBatchFile({unpack(array.sub(cmd2,1,#cmd2-1)), 'python /tmp/temp.py'})
			--os.execute('ssh localhost bash '..fn)
			os.execute('python /tmp/temp.py')
			os.execute('rm /tmp/temp.py')
			--return os.execute2()
		else
			return os.execute(cmd)
		end
	end
end
function git_tools.getUntracked(path)
	-- git-svn or git is used as opposed to the plain svn
	local status= string.lines(git_tools.capture('cd "'..path..'";git status -s .', true))
	--print('stat=',status[2])
	local untracked={}
	for il, line in ipairs(status) do
		local s,e,c=string.find(line, "%?%? (.+)")
		if s then
			untracked[#untracked+1]=c
		end
	end
	return untracked
end
function git_tools.getFilesToCommit(path)
		status= string.lines( git_tools.capture('cd "'..path..'";git status .', true))
		local modified={}
		for il, line in ipairs(status) do
			local output='modified: '
			local s,e,c=string.find(line, '#%s+modified:%s+(.+)')
			if not c then
				s,e,c=string.find(line, '%s+modified:%s+(.+)')
				output='new file: '
			end
			if not c then
				s,e,c=string.find(line, '#%s+new file:%s+(.+)')
				output='new file: '
			end
			if not c then
				s,e,c=string.find(line, '%s+new file:%s+(.+)')
				output='new file: '
			end
			if not c then
				s,e,c=string.find(line, '#%s+renamed:%s+(.+)')
				output='renamed: '
			end
			if not c then
				s,e,c=string.find(line, '%s+renamed:%s+(.+)')
				output='renamed: '
			end
			if not c then
				s,e,c=string.find(line, '#%s+both modified:%s+(.+)')
				output='both modified: '
			end
			if not c then
				s,e,c=string.find(line, '%s+both modified:%s+(.+)')
				output='both modified: '
			end
			if not c then
				s,e,c=string.find(line, '#%s+both added:%s+(.+)')
				output='both added: '
			end
			if not c then
				s,e,c=string.find(line, '%s+both added:%s+(.+)')
				output='both added: '
			end
			if not c then
				s,e,c=string.find(line, '#%s+both modified:%s+(.+)')
				if c then
					print('warning! '..c..' is an unmerged path')
				end
			end
			if c then 
				if not string.isMatched(os.filename(c), g_commitIgnorePattern) then
					modified[#modified+1]=c
					print(output .. c)
				end
			end
		end
		return modified
end
function git_tools.commit(path,force)
	if os.isFileExist(path..'/.git/HEAD') then
		if hookBeforeCommit then
			hookBeforeCommit(arg)
		end
		local untracked=git_tools.getUntracked(path)
		if #untracked>=1 then
			print('Untracked files:')
			print('----------------------------------')
			for i,v in ipairs(untracked) do print(v) end
			print('----------------------------------')
			print('Do you want to add these untracked files to git repository? Press enter if you are not sure. ([no]/edit/ignore/YES)')
			local cmd=''
			for i, v in ipairs(untracked) do 
				cmd=cmd.. git()..' add "'..v..'"\n'
			end
			local ioread
			if force then
				ioread='no'
			else
				ioread=io.read('*line')
			end
			if string.sub(ioread,1,3)=="YES" then
				os.execute2('cd "'..path..'"', cmd)
				print('\n Added '..#untracked..' files')
			elseif string.sub(ioread,1,4)=="edit" then
				os.createBatchFile("gitscript.bat", {'cd "'..path..'"',cmd})
				os.execute2('vim gitscript.bat')
				if os.isUnix() then
					os.execute2('sh gitscript.bat')
				else
					os.execute('gitscript')
				end
				os.deleteFiles('gitscript')
			elseif string.sub(ioread, 1,6)=='ignore' then
				local files=table.concat(untracked, '\n')
				local gitignore=''	
				if os.isFileExist(git_top()..'/.gitignore') then
					gitignore=util.readFile(git_top()..'/.gitignore')
					util.writeFile(git_top()..'/.gitignore.bak', gitignore)
				end
				if string.sub(gitignore, -1,-1)~='\n' then
					gitignore=gitignore..'\n'
				end
				util.writeFile(git_top()..'/.gitignore', gitignore..files..'\n')
			else
				print('\n ignoring all untracked files')
			end
			print('\n\n-----------------------')
		end
		local modified=git_tools.getFilesToCommit(path)
		if #modified>=1 then
			--printTable(modified)
			local bDone
			repeat
				bDone=true
				print('Do you want to commit these files to git repository? (yes/[no]/diff optionally followed by a commit message)')
				local ioread
				if force then
					ioread='yes'..table.concat(array.sub(arg, 2), " ")
				else
					ioread=io.read('*line')
				end
				if string.sub(ioread,1,3)=="yes" or string.sub(ioread,1,1)=='y' then
					local msg=string.sub(ioread,4)
					if msg=='' then
						msg='(no msg)'
					end
					for im, m in ipairs(modified) do
						git_tools.execute('cd "'..path..'";git add "'..m..'"')
					end
					git_tools.execute('cd "'..path..'";git commit -m "'..msg..'"')
					if path~="n" then
						if useGitSVN then
							print('Do you want to commit these files to SVN server? (yes/no)')
							local ioread=io.read('*line')
							if string.sub(ioread,1,3)=='yes' then
								os.execute('cd "'..path..'";git svn dcommit')
							end
						end
					end
					print('\n\n-----------------------')

					for im, m in ipairs(modified) do
						print('"'..m..'" added')
					end
					git_tools.execute('cd "'..path..'";git status . -s')
				elseif string.sub(ioread,1,1)=="d" then
					git_tools.execute('cd "'..path..'";git diff')
					bDone=false
				end 
			until bDone
		else
			print('No locally modified files\n')
			git_tools.execute('cd "'..path..'";git status . -s')
			if useGitSVN then
				os.execute('cd "'..path..'";git diff refs/remotes/git-svn')
				print('Do you want to commit to SVN server? (yes/no)')
				local ioread=io.read('*line')
				if string.sub(ioread,1,3)=='yes' then
					os.execute('cd "'..path..'";git svn dcommit')
				end
			end
		end
		if hookAfterCommit then
			hookAfterCommit(arg)
		end
	end
end
function git_tools.push(path, password)
	if os.isFileExist(path..'/.git/HEAD') then
		local remotes=array.filter(function (a) return string.len(a)>1 end, string.lines(git_tools.capture('cd "'..path..'";git remote',true)))
		if arg[2] and arg[2]~='-p' then
			if string.sub(arg[2],1,1)==':' then
				password=string.sub(arg[2],2)
			else
				remotes=array.filter(function (a) return select(1,string.find(a,arg[2])) end, remotes)
			end
		end
		local curr_branch=array.filter(function (a) return string.sub(a,1,1)=='*' end ,  string.lines(git_tools.capture('cd "'..path..'";git branch',true)))[1]
		curr_branch=string.sub(curr_branch,2)
		for k,v in ipairs(remotes) do
			local cmd='cd "'..path..'";git push '..v..curr_branch
			print('> '..cmd)
			git_tools.execute(cmd, password)
		end
	end
end

function getHashTbl(list, conversionFcn)
	if conversionFcn==nil then
		conversionFcn=function (v) return v end
	end

	local hcache={}
	for i,v in ipairs(list) do
		hcache[conversionFcn(v)]=true
	end
	return hcache
end
function interactiveChoose(fn, fromHistory)
	local index=1
	local function loadHistory(currDir)
		local hcache={_list={}}
		local history={}
		if os.isFileExist(g_cache_folder..'cache_hist.txt') then
			history= table.fromstring(util.readFile(g_cache_folder..'cache_hist.txt'))
			hcache=getHashTbl(history, 
			function (v) 
				v[1]=os.absoluteToRelativePath(v[1], currDir)
				return v[1]
			end
			)
			local fncache=getHashTbl(fn, 
			function (v)
				return v[1]
			end
			)
				
			hcache._list={}
			for i,v in ipairs(history) do
				if fncache[v[1]] then
					hcache._list[#hcache._list+1]=v
				end
			end
		end
		return history, hcache
	end
	local currDir=os.currentDirectory()
	local history, hcache=loadHistory(currDir)
	if #fn==0 then
		if keys then
			fn=chooseFileFromTagFallbackPath()
		end

		if #fn==0 then
			print('Choose from History:')
			fn=history
			if fn[2] then
				local temp=fn[1]
				fn[1]=fn[2] fn[2]=temp
				hcache._list=fn
			end
		end
	end
	-- reorder fn based on history
	local nhist=#hcache._list
	do
		local fn2={}
		local c=1
		--for i,v in ipairs(hcache._list) do
		--	fn2[#fn2+1]=v
		--end
		for i,v in ipairs(fn) do
			if hcache[v[1]] then 
				fn2[#fn2+1]=v
			end
		end
		for i,v in ipairs(fn) do
			if not hcache[v[1]] then 
				fn2[#fn2+1]=v
			end
		end
		fn=fn2
	end

	while #fn>1 do
		-- start file choose interface
		print('--------------------------------------------------------------')
		local c=1
		for i,v in ipairs(fn) do
			if c==1 then --and c<=nhist then
				print(v[1], "("..tostring(c).." - default)") 
			else
				print(v[1], "("..tostring(c)..")") 
			end
			if c==nhist then
				print('--------------------------------------------------------------')
			end
			c=c+1
			if c>20 then
				print('... not displaying more candidates') 
				break
			end
		end
		print('Enter file shortcut (shown on the right) or keyword to further refine the search:')
		local cmd=io.read('*line')
		index=tonumber(cmd) 

		if cmd=="" then 
			--if fromHistory or nhist~=0 then
				index=1
				break
			--else
			--	return nil 
			--end
		elseif index==nil then 
			-- refine search
			local refinedFn={}
			for i,v in ipairs(fn) do
				local idx=string.findLastOf(string.lower(v[1]), string.lower(cmd))
				if idx~=nil then
					array.pushBack(refinedFn, v)
				end
			end
			fn=refinedFn
			index=1
			if #fn==0 then return nil end
		else
			break
		end
	end
	do -- add to history
		if #fn==0 then return nil end
		if fn[index]==nil then return nil end
		local chosenFile=fn[index][1]
		if not history then history=loadHistory() end
		local max_num_hist=10
		history=array.filter(function (x) return x[1]~=chosenFile end, history)
		history={{chosenFile}, unpack(history)}
		for i =1, #history do
			history[i]={os.relativeToAbsolutePath(history[i][1], currDir)}
		end
		history[max_num_hist+1]=nil
		util.writeFile(g_cache_folder..'cache_hist.txt', table.tostring(history))
	end

	return fn[index]
end
function interactiveTagChoose(fn, inputs)
	local index=1
	local print=print
	local getInput=function() return io.read('*line') end
	local _currentInput=0

	if inputs then 
		print=function() end
		getInput=function()
			--dbg.console()
			_currentInput= _currentInput+1
			return inputs[_currentInput]
		end
	end

	local function cleanUpFunctionName(v)
		-- remove function definition
		local s,e=string.find(v[2], '{')
		if s then
			v[2]=string.sub(v[2], 1,s-1)
		end
		if _currentInput==0 then
			-- remove long arguments
			local s=string.tokenize(v[2],',')
			if #s>2 then
				v[2]=s[1]..','..s[2]..',...'
			end
		end
		v[2]=string.gsub(v[2], '%s+',' ')
		v[2]=string.trimSpaces(v[2])
	end
	local maxColumn=60
	local maxRow=20
	if isWindows() then 
		maxColumn=40
		maxRow=10
	end
	while #fn>1 do
		-- start file choose interface
		print('--------------------------------------------------------------')
		local c=1
		local prevFn
		for i,v in ipairs(fn) do
			local printFn
			if prevFn==v[3] and v[4]~=1 then
				printFn='    + '..v[4]
			else
				printFn='    | '..v[3]
				prevFn=v[3]
			end
				
			cleanUpFunctionName(v)
			if c==1 then
				print("["..tostring(c).."]  "..string.prettyConcat(v[2],printFn,maxColumn))
			else
				print("("..tostring(c)..")  "..string.prettyConcat(v[2],printFn,maxColumn))
			end
			c=c+1
			if c>maxRow then
				print('... not displaying more candidates') 
				break
			end
		end
		print('Enter file shortcut (shown on the right) or keyword to further refine the search:')
		local cmd=getInput()
		index=tonumber(cmd) 

		if cmd=="" then 
			index=1
			break
		elseif cmd==nil then
			-- prints
			for i,v in ipairs(fn) do
				cleanUpFunctionName(v)
				io.write(string.prettyConcat(v[2],' | '.. v[3], maxColumn)..'\n')
			end
			return nil
		elseif index==nil then 
			-- refine search
			local refinedFn={}
			for i,v in ipairs(fn) do
				local idx=string.find(string.lower(v[2]), string.lower(cmd))
				if idx~=nil then
					array.pushBack(refinedFn, v)
				else 
					idx=string.findLastOf(string.lower(os.filename(v[3])), string.lower(cmd))
					if idx~=nil then array.pushBack(refinedFn, v) end
				end
			end
			fn=refinedFn
			index=1
			if #fn==0 then return nil end
		else
			break
		end
	end
	return fn[index]
end
function findFileFromGit(key, ...)
	local fn
	do
		local str=os.capture(git()..' ls-files ../..', true)
		local files=string.lines(str)
		fn={}
		local keys={key,...}
		for ifile, file in ipairs(files) do
			if select(1,string.find(file, "%.lua$")) 
				or select(1,string.find(file, "%.c$")) 
				or select(1,string.find(file, "%.h$")) 
				or select(1,string.find(file, "%.hpp$")) 
				or select(1,string.find(file, "%.txt$")) 
				or select(1,string.find(file, "%.inl$")) 
				or select(1,string.find(file, "%.cpp$")) then
				for ikey,key in ipairs(keys) do
					if select(1,string.find(string.lower(file), string.lower(key))) then
						fn[#fn+1]={file}
						break
					end
				end
			end
		end
	end
	return interactiveChoose(fn, fromHistory)
end

function lsFiles(option, path)
	path=path or git_top()
	local cpt
	if cache_git_top=='' then
		--cpt=os.capture(git()..' ls-files .', true)
		if os.isApple() then
			cpt={}
		else
			cpt=os.glob('*',true,true)
		end
		printTable(cpt)
	elseif option=='-l' then	
		cpt=os.capture(git()..' ls-files .', true)
	elseif option=='-g' then
		-- use absolute
		cpt=os.capture('cd '..path..';git ls-files .', true)
	else
		cpt=os.capture(git()..' ls-files "'..git_top()..'" 2>&1', true)
		if select(1, string.find(cpt, 'fatal:')) then
			option='-g'
			cpt=os.capture('cd '..git_top()..';git ls-files .', true)
		end
	end
	return _lsFiles(cpt, option, path)
end

function _lsFiles(cpt, option, path)
	local files
	path=path or git_top()
	if type(cpt)=='string' then
		files=string.lines(cpt)
	else
		files=cpt
	end
	local files=array.sub(files,1,-2)
	if option=='-g' then
		for i,v in ipairs(files) do
			files[i]=path..'/'..v
		end
	end
	if #g_ignorePattern>0 then
		files=array.filter(function (fn)
			for i,pattern in ipairs(g_ignorePattern) do
				if select(1,string.find(fn, pattern)) then
					return false
				end
			end
			return true
		end, files)
	end
	if #files<100 then
		table.sort(files, git_tools.compareFileName)
	end
	return files
end

function lsFolders(option, lsFiles_fcn)
	local lsFiles=lsFiles_fcn or lsFiles
	local tbl=lsFiles(option)
	local folders={}
	for i,v in ipairs(tbl) do
		local fn, folder=os.processFileName(v)
		folders[folder]=true
	end
	local tbl={}
	for k,v in pairs(folders) do
		array.pushBack(tbl, k)
	end
	table.sort(tbl, git_tools.compareFileName)
	return tbl
end
function chooseFolder(keys)
	local folders=lsFolders()

	local fn={}
	for ifolder, folder in ipairs(folders) do
		for ikey,key in ipairs(keys) do
			local fname=os.filename(folder)
			if select(1,string.find(string.lower(fname), string.lower(key))) then
				fn[#fn+1]=folder
				break
			end
		end
	end
	local index=1
	local print=print
	local getInput=function() return io.read('*line') end
	local _currentInput=0

	while #fn>1 do
		-- start file choose interface
		print('--------------------------------------------------------------')
		local c=1
		for i,v in ipairs(fn) do
			if c==1 then
				print("["..tostring(c).."]  "..v)
			else
				print("("..tostring(c)..")  "..v)
			end
			c=c+1
			if c>20 then
				print('... not displaying more candidates') 
				break
			end
		end
		print('Enter file shortcut (shown on the right) or keyword to further refine the search:')
		local cmd=getInput()
		index=tonumber(cmd) 

		if cmd=="" then 
			index=1
			break
		elseif index==nil then 
			-- refine search
			local refinedFn={}
			for i,v in ipairs(fn) do
				local idx=string.find(string.lower(v), string.lower(cmd))
				if idx~=nil then
					array.pushBack(refinedFn, v)
				else 
					idx=string.findLastOf(string.lower(v), string.lower(cmd))
					if idx~=nil then array.pushBack(refinedFn, v) end
				end
			end
			fn=refinedFn
			index=1
			if #fn==0 then return nil end
		else
			break
		end
	end
	return fn[index]
end
function chooseFileFromTagFallbackPath()
	local fn
	for i=1,#g_tagFallbackPath do
		local path=os.relativeToAbsolutePath(g_tagFallbackPath [i], git_top())
		if os.isFileExist(path) then
			local keys={'-g', unpack(table.isubset(arg,2))}
			fn=nonInteractiveChoose(keys, path)
			if #fn>0 then break end
		end
	end
	return fn
end
function chooseFile(keys)
	local fn
	if #keys==0 then
		-- choose from history
		return interactiveChoose({})
	elseif #keys==1 then 
		local keys1=string.sub(keys[1],1,1)
		if keys1=='.' or keys1=='~' or keys1=='/' then
			if os.isFileExist(keys[1]) then
				fn={keys}
			end
		end
	end
	if not fn then
		fn=nonInteractiveChoose(keys)
	end
	if #fn==0 and keys then
		fn=chooseFileFromTagFallbackPath()
	end
	local chosen=interactiveChoose(fn)
	return chosen
end
function nonInteractiveChoose(keys, path)
	local option
	if keys[1]=='-l' then
		option='-l'
		keys=table.isubset(keys,2)
	end
	if #keys==0 then return {} end
	local useFull=false
	for ikey,key in ipairs(keys) do
		if select(1, string.find(key, '/')) then
			useFull=true
			keys[ikey]=string.gsub(key,'%.%./','')
			keys[ikey]=string.gsub(keys[ikey],'%./','')
		end
	end
	if useFull then option='-g' end
	if keys[1]=='-g' then
		option='-g'
		keys=table.isubset(keys,2)
		useFull=true
	end
	local files=lsFiles(option, path)

	local fn={}
	for ifile, file in ipairs(files) do
		if string.isMatched(file,g_vimSrc)  then
			local filename=os.filename(file)
			if useFull then
				filename=file
			end
			for ikey,key in ipairs(keys) do
				if select(1,string.find(string.lower(filename), string.lower(key))) then
					fn[#fn+1]={file}
					break
				end
			end
		end
	end
	return fn
end
function serverLaunched(name)
	local out=string.tokenize(os.capture(g_vimpath..' --serverlist',true), "\n")
	if name==nil then
		if #out>1 then
			return out[1]
		end
	else
		out=array.filter(function (v) return name==v end, out)
		if #out>0 then
			return name
		end
	end
	return nil
end
local function serverWithFileOpened(serverlist, fn)
	local out=serverlist
	if #out>0 then
		fn=os.relativeToAbsolutePath(fn)
		local allOpened={}
		for i=1,#out do
			local cmd=g_vimpath..' --servername '..out[i]..' --remote-expr "bufexists(\\\"'..fn..'\\\")"'
			local opened
			if isWindows() then
				opened=os.capture('"'..cmd)=='1'
			else
				opened=os.capture(cmd)=='1'
			end
			if opened then
				table.insert(allOpened, out[i])
			end
		end
		if #allOpened>0 then
			return allOpened
		end
	end
	return {} 
end
function makeEditable(servername, fullfn)
	-- 1. select the buffer 
	local cmd=g_vimpath..' --servername '..servername..' --remote-send "<ESC>:b '..fullfn..'<CR>"'
	print(cmd)
	os.execute(cmd)
	-- 2. check if readonly
	cmd=g_vimpath..' --servername '..servername..' --remote-expr "&readonly"'
	print(cmd)
	if os.capture(cmd)=='1' then
		-- set writable
		local cmd=g_vimpath..' --servername '..servername..' --remote-send ":setlocal noreadonly<CR>:setlocal modifiable<CR>:setlocal noautoread<CR>'
		os.execute(cmd)
	end
end
function makeReadOnlyAll(servers, fullfn)
	local debugMode=false
	local function isSwapExist(fullfn)
		if os.isFileExist(path..'/.'..fn..'.swo') then return true end
		if os.isFileExist(path..'/.'..fn..'.swp') then return true end
		return false
	end
	local function deleteSwap(fullfn)
		local fn, path=os.processFileName(fullfn)
		if os.isFileExist(path..'/.'..fn..'.swo') then
			os.deleteFiles(path..'/.'..fn..'.swo')
		end
		if os.isFileExist(path..'/.'..fn..'.swp') then
			os.deleteFiles(path..'/.'..fn..'.swp')
		end
	end
	local function makeReadOnly(servername, fullfn)
		-- 1. select the buffer after setting a global marker
		local cmd=g_vimpath..' --servername '..servername..' --remote-send "<ESC>mZ:b '..fullfn..'<CR>"'
		if debugMode then print(cmd) end
		os.execute(cmd)
		-- 2. check if readonly
		cmd=g_vimpath..' --servername '..servername..' --remote-expr "&readonly"'
		if debugMode then print(cmd) end
		if os.capture(cmd)=='1' then
			-- already readonly. simply go back to the marker position
			local cmd=g_vimpath..' --servername '..servername..' --remote-send "\'Z"'
			if debugMode then print(cmd) end
			os.execute(cmd)
		else
			-- write 
			local cmd=g_vimpath..' --servername '..servername..' --remote-send ":w<CR>:setlocal readonly<CR>:setlocal nomodifiable<CR>:setlocal autoread<CR>\'Z"'
			if debugMode then print(cmd) end
			os.execute(cmd)
		end
	end
	local allOpened =serverWithFileOpened(servers, fullfn)
	if allOpened then
		for i,opened in ipairs(allOpened) do
			makeReadOnly(opened, fullfn)
		end
		deleteSwap(fullfn)
	end
end
function gvimFile(servername, chosen)
	if isWindows() then
		local chosenfile=os.toWindowsFileName(chosen)
		if not serverLaunched(servername) then
			local cmd=g_gvimpath..' --servername '..servername..' "'..chosenfile..'"'
			-- launch gvim
			local fn=os.createUnnamedBatchFile({cmd})
			os.execute_original('start /b cmd /c "'..fn..'"')
		else
			os.execute(g_gvimpath..' --servername '..servername..' --remote-silent "'..chosenfile..'"')
		end
	else
		if os.isCygwin() or not serverLaunched(servername) then
			-- launch gvim
			os.execute(g_gvimpath..' --servername '..servername..' -f "'..chosen..'"&')
		else
			--[[
			while(true) do
				if serverLaunched(servername) then
					break
				end
			end
			]]--

			os.execute(g_gvimpath..' --servername '..servername..' --remote-silent "'..chosen..'"&')
		end
	end
end

function os.gvim_remote(servername, filename)
	local out=string.tokenize(os.capture(g_vimpath..' --serverlist',true), "\n")
	local otherServers=array.filter(function(v)return v~=servername  end, out)
	local thisServer=array.filter(function(v)return v==servername  end, out)
	otherServers[#otherServers]=nil
	--[[
	printTable(out)
	print('otherServers')
	printTable(otherServers)
	print('thisServer')
	printTable(thisServer)
	]]--
	local fullfn=os.relativeToAbsolutePath(filename)
	makeReadOnlyAll(otherServers, fullfn)
	if thisServer and #thisServer>0 and #serverWithFileOpened(thisServer, fullfn)>0 then
		-- already open on this server so just choose
		makeEditable(servername, fullfn)
	else
		gvimFile(servername, filename)
	end
end

function os.vi_remote(filename, line)
	if g_useEmacs then
		local cmd
		if line then
			cmd='emacsclient -n +'..line..' "'..filename..'"'
		else
			cmd='emacsclient -n "'..filename..'"'
		end
		local output=os.capture(cmd..' 2>&1')
		if select(1, string.find(output, 'To start the server')) then 
			os.execute('emacs --eval "(server-start)"&')
			-- now have to wait, so removing -n option
			cmd=string.gsub(cmd, 'client -n', 'client')
			local c=1
			for i=1,100 do -- try 100 times
				os.sleep(1)
				local output=os.capture(cmd..' 2>&1')
				if not select(1, string.find(output, 'To start the server')) then 
					c=c+1
					if c==3 then -- heuristic.
						return
					end
				end
			end
		end
		return
	elseif g_useSublime then
		local cmd
		if line then
			cmd='/opt/sublime_text/sublime_text "'..filename..'":'..line
		else
			cmd='/opt/sublime_text/sublime_text "'..filename..'"'
		end
		os.execute(cmd..'&')
		return
	end
	if os.isApple() then
		local vimpath=g_vimpath
		if line then
			os.execute(vimpath..' +'..line..' "'..filename..'"')
		else
			os.execute(vimpath..' "'..filename..'"')
		end
		return
	end
	local servername=serverLaunched()
	local vimpath=g_vimpath
	if servername then
		print("sent "..filename.." to the vim server")
		if line then
			os.execute(vimpath..' --servername '..servername..' --remote +'..line..' "'..filename..'"')
		else
			os.execute(vimpath..' --servername '..servername..' --remote "'..filename..'"')
		end
	else
		local vis=vimpath..' --servername VIM '
		if os.isCygwin() then vis=vimpath end
		-- launch vim
		if line then
			os.execute(vis..' +'..line..' "'..filename..'"')
		else
			os.execute(vis..' '..filename..'"')
		end
	end
end
function git_tools.tagChooseFromFile(keyword, keyword2, path)
	print('Searching '..keyword)
	if not os.isFileExist(path..'/TAGS') then
		print('Generating '..path..'/TAGS...')
		main('etags', path)
	end
	local chosen=interactiveTagChoose(git_tools.searchTag(path..'/TAGS', keyword))
	return chosen
end
function git_tools.tagChoose(keyword, keyword2)
	if keyword==nil then
		printHelp()
		return nil
	end

	local path=git_top()
	if keyword=='-l' then
		path='.'
		keyword=keyword2
	end
	local chosen= git_tools.tagChooseFromFile(keyword, keyword2, path)
	if not chosen then
		for i=1,#g_tagFallbackPath do
			local path=git_top()..'/'..g_tagFallbackPath [i]
			if os.isFileExist(path) then
				print('tag searching '..g_tagFallbackPath[i])
				chosen= git_tools.tagChooseFromFile(keyword, keyword2, path)
				if chosen then
					break
				end
			end
		end
	end
	return chosen
end
function git_tools.tagList(keywords)
	if keywords[1]==nil then
		printHelp()
		return nil
	end
	if not os.isFileExist(git_top()..'/TAGS') then
		print('Generating '..git_top()..'/TAGS...')
		main('etags', git_top())
	end
	interactiveTagChoose(git_tools.searchTag(git_top()..'/TAGS', keywords[1]), array.sub(keywords,2))
end

function main(...)
	if os.isFileExist(git_top()..'/.gitvconfig') then
		dofile(git_top()..'/.gitvconfig')
	end
	local arg={...}
	if arg[1]=="vi" then
		local keys=array.sub(arg,2)
		local chosen=chooseFile(keys)
		if chosen then
			os.execute(g_vimpath..' "'..chosen[1]..'"')
		end
	elseif arg[1]=='unzip' then -- iconv after unzip. only for Korean cygwin users.
		if not os.isCygwin() and not os.isUnix()then
			print('Error! only for cygwin')
			return
		end
		if not os.isFileExist('/usr/bin/unzip') then
			print('Error! install unzip and convmv using cygwin setup.exe')
			return
		end

		if not os.isFileExist('/usr/bin/convmv') then
			print('Error! install convmv using cygwin setup.exe')
			return
		end
		local fn=arg[2]
		if string.lower(string.sub(fn,-4))~='.zip' then
			if os.isFileExist(fn..'.zip') then
				fn=fn..'.zip'
				print(fn)
			else
				print('Error! not a zip file?')
				return
			end
		end
		local unzippath=string.sub(fn, 1,-5)
		if os.isFileExist(unzippath) then
			print('Error! '..unzippath..' exists')
			return
		end
		os.execute('mkdir -p "'..unzippath..'"')
		os.execute('cd "'..unzippath..'";unzip "../'..fn..'"')
		os.execute('cd "'..unzippath..'";convmv -r --notest -f cp949 -t utf-8 .')
	elseif arg[1]=='stat' then
		os.execute('git diff --stat')
		print('press Enter') io.read('*l')
		os.execute('git status .')
	elseif arg[1]=="vir" then
		local keys=array.sub(arg,2)
		local chosen
		local fn=nonInteractiveChoose(keys)
		if #fn>=1 and #array.filter(function(f) return f[1]==arg[2] end, fn)>=1 then 
			chosen={arg[2]} 
		else 
			chosen=interactiveChoose(fn) 
		end
		if chosen then
			os.vi_remote(chosen[1])
		end
	elseif arg[1]=='open' then
		os.vi_remote(arg[2])
	elseif arg[1]=="ts" then
		local chosen=git_tools.tagChoose(arg[2], arg[3])
		if chosen then
			os.vi_remote(chosen[3],chosen[4])
		end
	elseif arg[1]=="tl" then
		git_tools.tagList(array.sub(arg,2))
	elseif arg[1]=="tschoose" then
		local chosen=git_tools.tagChoose(arg[2])
		if chosen then
			util.writeFile(g_tmp_folder..'gitv_script', 
			'edit '..chosen[3]..'\n:' ..tostring(chosen[4]))
		else
			local key=string.gsub(arg[2],"%$", "")
			local key=string.gsub(key,"%^", "")
			print("Tags "..key.." not found. Fallback to the file chooser!")
			local chosen=chooseFile({ key})
			if chosen then
				util.writeFile(g_tmp_folder..'gitv_script', 
				'edit '..chosen[1]..'\n:1')
			end
		end
	elseif arg[1]=="macro_commentout" then
		local fn=arg[2]
		local key=arg[3]
		unmacro(fn,key)
	elseif arg[1]=="activate_macro" then
		local fn=arg[2]
		local key=arg[3]
		macro(fn,key)
	elseif arg[1]=="gedit" then
		local keys=array.sub(arg,2)
		local chosen=chooseFile(keys)
		if chosen then
			os.execute('gedit "'..chosen[1]..'"&')
		end
	elseif arg[1]=="gvim" then
		local keys=array.sub(arg,2)
		local chosen=chooseFile(keys)
		if chosen then
			gvimFile('GVIM', chosen[1])
		end
	elseif arg[1]=="gvimr" then
		local servername=arg[2]
		local keys=array.sub(arg,3)
		--if keys[1]==nil then printHelp() return end
		local chosen=chooseFile(keys)
		if chosen then
			os.gvim_remote(servername, chosen[1])
		end
	elseif arg[1]=="_gvimr" then
		print("Servername: ", arg[2])
		print("Filename: ", arg[3])
		os.gvim_remote(arg[2], arg[3])
	elseif arg[1]=="choose" then
		local keys=array.sub(arg,2)
		local chosen=chooseFile(keys)
		if chosen then
			util.writeFile(g_tmp_folder..'gitv_script', 'edit '..chosen[1])
		end
	elseif arg[1]=="listFolder" then
		local keys=array.sub(arg,2)
		local fns=nonInteractiveChoose(keys)
		if fns  then
			for i,v in ipairs(fns) do
				local fn, path=os.processFileName(v[1])
				if path=="" then
					path='.'
				end
				print(path)
			end
		end
	elseif arg[1]=="chooseFolder" then
		local keys=array.sub(arg,2)
		local chosen=chooseFile(keys)
		if chosen then
			local fn, path=os.processFileName(chosen[1])
			util.writeFile(g_tmp_folder..'gitv_chosen', path)
		else
			util.writeFile(g_tmp_folder..'gitv_chosen', '.')
		end
	elseif arg[1]=="chooseFolder2" then
		local keys=array.sub(arg,2)
		local chosen=chooseFolder(keys)
		if chosen then
			local t=string.gsub(chosen,'~', os.home_path())
			util.writeFile(g_tmp_folder..'gitv_chosen', t)
		else
			util.writeFile(g_tmp_folder..'gitv_chosen', '.')
		end
	elseif arg[1]=="chooser" then
		local servername=arg[2]
		local keys=array.sub(arg,3)
		local chosen=chooseFile(keys)
		if chosen then
			local out
			if os.isCygwin() then
				out={servername}
			else
				out=string.tokenize(os.capture(g_vimpath..' --serverlist',true), "\n")
			end
			local otherServers=array.filter(function(v)return v~=servername  end, out)
			local thisServer=array.filter(function(v)return v==servername  end, out)
			otherServers[#otherServers]=nil
			thisServer[#thisServer]=nil
			local fullfn=os.relativeToAbsolutePath(chosen[1])
			makeReadOnlyAll(otherServers, fullfn)
			util.writeFile(g_tmp_folder..'gitv_script', 'edit '..chosen[1]..
			'\n:setlocal noreadonly\n:setlocal modifiable\n:setlocal noautoread')
		end
	elseif arg[1]=='find' then
		local keys=array.sub(arg,2)
		findFile(keys, print)
	elseif arg[1]=='diffrepo' or arg[1]=='cprepo' or arg[1]=='cprepo2' then
		local filen=arg[2]
		local statOnly=false

		if filen=="--stat" then
			statOnly=true
			filen=arg[3]
			arg[3]=arg[4]
			arg[4]=arg[5]
		end
		local other_repo=arg[3]
		local function printHelp(verb, cmd)
			print('Diff a file in a repository to another.')
			print('usage: gitv ' .. cmd ..' file1 path_to_other_repo_relative_to_git_root')
			print('   or  gitv ' .. cmd ..' file1 ')
			print('   or  gitv ' .. cmd ..' .')
			return
		end
		if not filen then
			if arg[1]=='diffrepo' then
				printHelp('Diff', 'diffrepo')
			else
				printHelp('Copy', 'cprepo')
				print("You can use cprepo2 to copy the file to the target repo")
			end
		end

		other_repo=other_repo or g_diffTarget
		if(other_repo==nil ) then
			print('set g_diffTarget in your $GIT_ROOT/.gitvconfig')
			print('For example,    g_diffTarget="../taesooLib"')
			print('Always use relative path name!')
			return
		end
		local file1=os.relativeToAbsolutePath(filen)
		local path1=git_top()
		local path2=string.gsub(file1, path1, other_repo)
		path2=os.relativeToAbsolutePath(path2, path1)

		if arg[1]=='cprepo' or arg[1]=='cprepo2' then
			if filen=='.' then
				local cpt1=os.capture('cd "'..path1..'";git ls-files .', true)
				if string.sub(path1,-1)~='/' then
					path1=path1..'/'
				end
				if string.sub(path2,-1)~='/' then
					path2=path2..'/'
				end
				local tbl=_lsFiles(cpt1)
				local tbl2={}
				for i=1, #tbl do
					tbl2[i]='cp "'..path1..tbl[i]..'" "'..path2..tbl[i]..'"'
				end

				util.writeFile('_copy.sh', table.concat(tbl2,'\n'))
				print('Run "sh _copy.sh" to copy!')
			else
				if arg[1]=='cprepo' then
					os.copyFile(path2, file1)
				else
					os.copyFile(file1, path2)
				end
			end
		else
			if filen=='.' then
				if os.capture("which meld")=="" then
					print("meld not found")
					local files=lsFiles('-l')
					path1=file1
					path1=string.gsub(path1, '%.','')
					path2=string.gsub(path2, '%.','')
					for i, fn in ipairs(files) do
						local t1=os.isFileExist(path1..fn)
						local t2=os.isFileExist(path2..fn)
						if t1 and t2 then
							local t=os.capture('diff "'..path1..fn..'" "'..path2..fn..'"')
							if t=="" then
								--print(fn..' identical')
							else
								if statOnly then
									print(fn .. ' different')
								else
									local cmd='vimdiff "'..path1..fn..'" "'..path2..fn..'"'
									print(cmd)
									os.execute(cmd)
								end
							end
						else
							if not t1 then
								print(path1..fn .." does not exist!")
							end
							if not t2 then
								print(path2..fn .." does not exist!")
							end
						end
					end
					--printTable(files)
				else
					os.execute('meld "'..file1..'" "'..path2..'"')
				end
			else
				local cmd='vimdiff "'..file1..'" "'..path2..'"'
				print(cmd)
				os.execute(cmd)
			end
		end
	elseif arg[1]=='test_win_filename' then
		local cpt=string.lines(os.capture('cd '..git_top()..';git ls-files .', true))
		local key={}
		for i,fn in ipairs(cpt) do
			local lfn=string.lower(fn)
			if key[lfn] then
				print('duplicated '..fn)
			else
				key[lfn]=true
			end
		end
	elseif arg[1]=="print" then
		local keys=array.sub(arg,2)
		local chosen=chooseFile(keys)
		if chosen then
			os.execute('vim -c ":hardcopy" -c ":q" "'..chosen[1]..'"')
			os.execute('nautilus ~/PDF')
		end
	elseif arg[1]=='sync' then
		if arg[2]=='.' then
			local untracked=git_tools.getUntracked('.')
			local filter=function(files)
				local filtered={}
				for i,v in ipairs(files) do
					if select(1,string.find(v, '/')==nil) then
						table.insert(filtered, v)
					end
				end
				return filtered
			end
			local untracked_local=filter(untracked)
			if #untracked_local>0 then
				printTable(untracked_local)
				print("Do you want to add these untracked files to git repository? ([yes]/no)")
				ioread=io.read('*line')
				if string.lower(ioread)~='no' then
					for i,v in ipairs(untracked_local) do
						os.execute(git()..' add "'..v..'"')
					end
					print(msg)
				end
			end
			local modified=filter(git_tools.getFilesToCommit('.'))
			if #modified>0 then
				printTable(modified)
				local bDone
				repeat
					bDone=true
					print("Do you want to commit these files to git repository? ([yes]/no/diff optionally followed by a commit message)")
					ioread=io.read('*line')
					if string.lower(string.sub(ioread, 1,2))=='no' then
					elseif string.lower(string.sub(ioread,1,1))=='d' then
						git_tools.execute(git()..' diff .')
						bDone=false
					else
						local msg=string.sub(ioread,4)
						if msg=='' then
							msg='(no msg)'
						end
						for im, m in ipairs(modified) do
							git_tools.execute(git()..' add "'..m..'"')
						end
						git_tools.execute(git()..' commit -m "'..msg..'"')
					end
				until bDone 
			end
			print("------------------------")
			print("Pull:")
			print("------------------------")
			git_tools.pull(git_top())
			print("------------------------")
			print("Push:")
			print("------------------------")
			git_tools.push(git_top())
		else
			local password
			if arg[2]=='-p' then
				print("Input password:")
				function getch_unix()
					os.execute("stty cbreak </dev/tty >/dev/tty 2>&1")
					local key = io.read(1)
					os.execute("stty -cbreak </dev/tty >/dev/tty 2>&1");
					return(key);      
				end 
				password=''
				while true do
					local c=getch_unix()
					if c =='\n' then
						break
					else
						password=password..c
						io.write('\r')
					end
				end
			end
			print("------------------------")
			print("Commit:")
			git_tools.commit(git_top())
			print("------------------------")
			print("Pull:")
			git_tools.pull(git_top(),password)
			print("------------------------")
			print("Push:")
			git_tools.push(git_top(),password)
		end
	elseif arg[1]=='pull' then
		git_tools.pull(git_top())
	elseif arg[1]=='push' then
		git_tools.push(git_top())
		if hookAfterPush then
			hookAfterPush(arg)
		end
	elseif arg[1]=='backup' then
		print('> git stash save; git stash apply')
		os.execute2(git()..' stash save', git() .. ' stash apply')
	elseif arg[1]=='commit' or arg[1]=='ci' then
		git_tools.commit(git_top())
	elseif arg[1]=='cif' then
		git_tools.commit(git_top(), true)
	elseif arg[1]=='undo-commit' then
		print("Do not undo commit if you pushed your last commit!")
		print("Are you sure? (yes/[no])")
		ioread=io.read('*line')
		if ioread=='yes' or ioread=='YES' then
			local cmd='git reset --soft HEAD^'
			print(cmd)
			os.execute(cmd)
			cmd='git reset'
			print(cmd)
			os.execute(cmd)
			print("Undoing finished.")
		else
			print("Undoing canceled.")
		end
	elseif arg[1]=='fetch' then
		git_tools.fetch(git_top())
	elseif arg[1]=="etags" or arg[1]=="ctags" then
		local isSourceCode=function(str)
			return string.isMatched(str,g_etagsSrc)
		end

		local files=array.filter(isSourceCode,lsFiles())
		if arg[2]=='.' then
			files=array.filter(function (f) local s=string.find(f, '^%.%.') return not s end, files)
		elseif arg[2] then
			--printTable(files)
			os.execute2('cd "'..arg[2]..'"', 'gitv etags')
			return
		end
		table.sort(files, git_tools.compareFileName)
		util.writeFile(g_cache_folder..'gitvsrclist', table.concat(files,'\n'))
		local cmd='--extra=+f --langmap=c++:.inl.h.hpp.c.cpp.cc.cxx -L '..g_cache_folder..'gitvsrclist'
		print(cmd)
		
		if isWindows() then
			cmd=string.gsub(cmd,'\\','/')
		end
		if g_customCtagsParam then
			cmd=g_customCtagsParam  .. ' '..cmd
		end
		if arg[1]=="ctags" then
			os.execute(g_ctags..' '..cmd ..' -f .tags')
		else
			os.execute(g_ctags..' -e '..string.gsub(cmd,'\\','/') )
		end
		local isCustomSourceCode=function(str)
			return string.isMatched(str,g_customEtagsSrc)
		end
 
		local files=array.filter(isCustomSourceCode,lsFiles())
		if arg[2]=='.' then
			files=array.filter(function (f) local s=string.find(f, '^%.%.') return not s end, files)
		elseif arg[2] then
			return
		end
		table.sort(files, git_tools.compareFileName)
		util.writeFile(g_cache_folder..'gitvsrclist', table.concat(files,'\n'))
		local cmd='--append=yes '..g_customEtagsPattern..' -L '..g_cache_folder..'gitvsrclist'
		print(cmd)
		
		if os.isWindows() then
			cmd=string.gsub(cmd,'\\','/')
		end
		if arg[1]=="ctags" then
			os.execute(g_ctags..' '..cmd ..' -f .tags')
		else
			os.execute(g_ctags..' -e '..cmd)
		end
	elseif arg[1]=='grep' then
		local files
		if arg[2]=='-l' then
			arg=table.isubset(arg,2)
			files=lsFiles('-l')
		else
			files=lsFiles()
		end
		if arg[2] then
			print('searching '..arg[2]..' in '..#files ..' files...')
			if select(1, string.find(arg[2], '"')) and not select(1, string.find(arg[2],'%[')) then
				-- treat " and ' same. (convenient for many script languages)
				arg[2]=string.gsub(arg[2], '"', '["\']')
			end
			for i,f in ipairs(files) do
				if string.isMatched(f,g_grepSrc) then
					util.grepFile(f, arg[2],'',true)
				end
			end
		end
	elseif arg[1]=='findgrep' then
		print('searching '..arg[3]..' in '..arg[2]..' ...')
		local files=lsFiles()
		local filefunc=function(f)
			grepFile(f, arg[3])
		end
		findFile({arg[2]}, filefunc)

	elseif arg[1]=='fgrep' then
		print('searching '..arg[2]..'...')
		local files=lsFiles()
		if arg[2] then
			for i,f in ipairs(files) do
				if string.isMatched(f,g_grepSrc) then
					util.grepFile(f, arg[2],'',false)
				end
			end
		end
	elseif arg[1]=='ls' then
		local files=lsFiles()
		for i,f in ipairs(files) do print (f) end
	elseif arg[1]=='clear-hist' then
		os.deleteFiles(g_cache_folder..'cache_hist.txt')
		os.deleteFiles(git_top()..'/TAGS')
	elseif arg[1]=='cmd' then
		local cmd={go=os.openFolder} 
		local fn=cmd[arg[2]]
		if arg[3] then fn(arg[3]) end
	elseif arg[1]=='diff' then
		local added=''
		if arg[2] then added=' '..arg[2] end
		if arg[3] then added=' "'..arg[3]..'"' end
		local stashlist=string.lines(os.capture(git()..' stash list', true))
		if #stashlist>1 then
			print(tostring(#stashlist-1)..' stashed backups.')
			local cmd=git()..' diff stash --stat'..added
			print('> '..cmd)
			print('---------------------------------------------------')
			local str1=os.capture(cmd, true)
			print(str1)
			print('---------------------------------------------------')
			print('> git diff --stat'..added)
			print('---------------------------------------------------')
			os.execute('git diff --stat')
			return
		end


		print('\n git diff --stat'..added)
		print('---------------------------------------------------')
		local str= os.capture(git()..' diff --stat'..added, true)
		print(string.sub(str,1,-2))
		if #string.lines(str)>1 then
			print('---------------------------------------------------')
			print(string.sub(str,1,-2))
			print('---------------------------------------------------')
			print('- do you want to see the differences? (yes/[no])')
			if io.read('*line')=='yes' then
				print('\n\n-------------------git difftool:-------------------')
				os.execute(git()..' difftool'..added)
			end
		else
			print('- no changes ')
		end
	elseif arg[1]=='kill' then
		local out=string.lines(os.capture('pgrep '..arg[2]))
		os.execute('kill -9 '..out[1])
	elseif arg[1]=='all' then
		local use_gitv_conf=os.isFileExist(os.home_path()..'/.gitv_conf') 

		if use_gitv_conf or g_gitPaths then

			local out
			if use_gitv_conf then
				out=string.lines(util.readFile(os.home_path()..'/.gitv_conf'))
				out[#out]=nil
			else
				out=g_gitPaths
			end
			for i,v in ipairs(out) do
				v=string.gsub(v, '~', os.home_path())
				local cmd='gitv '..table.concat(array.sub(arg,2), ' ')
				print('\n> cd "'..v..'";'..cmd)
				os.execute2('cd "'..v..'"', cmd)
			end
		else
			print('Cannot find ~/.gitv_conf')
			print('  list git repositories seperated by new lines in ~/.gitv_conf')
		end
	elseif arg[1]=='post-change-message' then
		if arg[2] then
			local cmd=
			'git commit --amend -m "'..arg[2]..'"'
			print(cmd)
			os.execute(cmd)
		end
	elseif arg[1]=='iconvcheck' then
		-- convert EUCKR encoding to UTF-8
		local option=''
		local force=false
		if arg[2]=='.' then
			option='-l'
		elseif arg[2]=='.f' then
			option='-l'
			force=true
		elseif arg[2]=='-f' then
			force=true
		elseif arg[2]=='-a' then
			local files=lsFiles(option)
			local fn={}
			for ifile, file in ipairs(files) do
				if string.isMatched(file,g_vimSrc)  then
					os.execute2('cp "'..file..'" "'..file..'.bak"',
					'iconv -f UTF8 -t ANSI_X3.4 -c "'..file..'.bak" -o "'..file..'"' )
				end
			end
			return
		end

		if force then
			if force then
				print('Force convert!')
			end
			if option~='-l' then
				print('All files!')
			end
			print('Are you sure? (YES/[no])')
			ioread=io.read('*line')
			if ioread~='YES' then
				force=false
			end
		end

		local files=lsFiles(option)

		local fn={}
		for ifile, file in ipairs(files) do
			if string.isMatched(file,g_vimSrc)  then
				local output=os.capture('iconv -f UTF-8 "'..file..'" -o /dev/null 2>&1')
				if output~='' then
					local trySourceEncodings={
						--koreans
						'EUCKR', 'CP949', 'MSCP949', 'ISO-2022-KR',
					}
					local autoConv=false
					for ienc, enc in ipairs(trySourceEncodings) do
						local output2=os.capture('iconv -f '..enc..' -t UTF8 "'..file..'" -o /dev/null 2>&1')
						if output2=='' then
							print("automatically converting "..file)
							os.execute2('cp "'..file..'" "'..file..'.bak"',
							'iconv -f '..enc..' -t UTF8 "'..file..'.bak" -o "'..file..'"' )
							autoConv=true
							break
						end
					end
					if not autoConv then
						if force then
							print("force converting "..file)
							os.execute2('cp "'..file..'" "'..file..'.bak"',
							'iconv -f UTF8 -t UTF8 "'..file..'.bak" -c -o "'..file..'"' )
						else
							print(file, output)
							local tokens=string.tokenize(output, '%s')
							local byte=tonumber(tokens[#tokens])
							print('---------------------------------')
							print(string.sub(util.readFile(file), byte-100, byte+100))
							print('---------------------------------')
						end
					end
				end
			end
		end

	else
		printHelp()
	end
end
function findFile(keys, filefunc)
		local files=lsFiles()
		local fn={}
		for ifile, file in ipairs(files) do
			local passed=true
			for j=1,#keys do
				if not select(1,string.find(string.lower(file), string.lower(keys[j]))) then
					passed=false
				end
			end
			if passed then
				filefunc(file)
			end
		end
end
function grepFile(f, key)
	if select(1, string.find(key, '"')) and not select(1, string.find(key,'%[')) then
		-- treat " and ' same. (convenient for many script languages)
		key=string.gsub(key, '"', '["\']')
	end
	if string.isMatched(f,g_grepSrc) then
		util.grepFile(f, key,'',true)
	end
end
if string.sub(arg[0],-4)~='gitv' then
	return
end
if arg[1] then
	if arg[1]=='--emacs' then
	   g_useEmacs=true
		main(unpack(array.sub(arg,2)))
   elseif arg[1]=='--sublime' then
	   g_useSublime=true
		main(unpack(array.sub(arg,2)))
	elseif arg[1]=='clone_subtree' then
		local repo=arg[2]
		local url=arg[3]
		local path=arg[4]
		if not url or not repo or not path then
			print('gitv clone_subtree repo url path')
			print('e.g. gitv clone_subtree SP_taesoo ssh://taesoo@calab.hanyang.ac.kr:8022/~/taesoo.git courses/SP_taesoo')
			return 
		end

		execute2('git init "'..repo..'"',
		'cd "'..repo..'"',
		'git remote add origin '..url, 'git config core.sparsecheckout true')
		execute2(
		'cd "'..repo..'"',
		'echo "'..path..'" > .git/info/sparse-checkout',
		'git pull origin master')
	elseif arg[1]=="get_subtree" then
		local repo=arg[2]
		local url=arg[3]
		local path=arg[4]
		if not url or not repo or not path then
			print('gitv get_subtree repo url path')
			print('e.g. gitv get_subtree SP_taesoo ssh://taesoo@calab.hanyang.ac.kr:8022/~/taesoo.git courses/SP_taesoo')
			return 
		end
		execute2('mkdir -p "'..repo..'"',
		'cd "'..repo..'"',
		'git archive --remote='..url ..' master '..path .. '| tar xvf -')
	else
		main(unpack(arg))
	end
else
	printHelp()
end
